package com.duolebo.uteped_sdk.msg;

import android.bluetooth.BluetoothDevice;
import android.text.TextUtils;

import androidx.annotation.NonNull;

import com.duolebo.uteped_sdk.UTEPedEventHandler;
import com.duolebo.uteped_sdk.remind.CameraRemind;
import com.duolebo.uteped_sdk.utils.BluetoothHelper;
import com.duolebo.uteped_sdk.utils.Const;
import com.duolebo.uteped_sdk.utils.DeviceHelper;
import com.duolebo.uteped_sdk.utils.PermissionHelper;
import com.duolebo.uteped_sdk.utils.SPHelper;
import com.duolebo.uteped_sdk.utils.Tools;
import com.google.gson.Gson;
import com.yc.nadalsdk.bean.BatteryInfo;
import com.yc.nadalsdk.bean.CameraControl;
import com.yc.nadalsdk.bean.CameraStatusConfig;
import com.yc.nadalsdk.bean.DefaultConfiguration;
import com.yc.nadalsdk.bean.DeviceInfo;
import com.yc.nadalsdk.bean.DeviceInfoRequest;
import com.yc.nadalsdk.bean.DevicePairedState;
import com.yc.nadalsdk.bean.DeviceResetNotify;
import com.yc.nadalsdk.bean.FindWearState;
import com.yc.nadalsdk.bean.HonorAccountConfig;
import com.yc.nadalsdk.bean.JLOtaSecondModeNotify;
import com.yc.nadalsdk.bean.Notify;
import com.yc.nadalsdk.bean.Response;
import com.yc.nadalsdk.bean.ScreenAutoLightSwitch;
import com.yc.nadalsdk.ble.open.UteBleConnection;
import com.yc.nadalsdk.ble.open.UteBleDevice;
import com.yc.nadalsdk.constants.NotifyType;
import com.yc.nadalsdk.listener.BleConnectStateListener;
import com.yc.nadalsdk.listener.DeviceNotifyListener;
import com.yc.nadalsdk.log.LogUtils;
import com.yc.nadalsdk.scan.UteScanCallback;
import com.yc.nadalsdk.scan.UteScanDevice;
import com.yc.nadalsdk.utils.open.SPUtil;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;

import com.duolebo.uteped_sdk.utils.BluetoothHelper.BluetoothOptionListener;

/**
 * @Author: LiuYang
 * @Date: 2023/12/11 12:59
 * @Description: 设备页面通信协议部分
 */
public class DeviceMessage extends BaseMessage implements BleConnectStateListener,
        UteScanCallback,
        DeviceNotifyListener,
        BluetoothOptionListener {
    private final static String PATH = "device";
    private final static String M_scanDevice = "scanDevice";
    private final static String M_cancelScan = "cancelScan";
    private final static String M_connect = "connect";
    private final static String M_disconnect = "disconnect";
    private final static String M_getDeviceInfo = "getDeviceInfo";
    private final static String M_getBatteryInfo = "getBatteryInfo";
    private final static String M_getBondedDevices = "getBondedDevices";
    private final static String M_getDefaultConfiguration = "getDefaultConfiguration";
    private final static String M_turnBluetooth = "turnBluetooth";

    private final static String KEY_BIND_BT3 = "bindBT3";
    private final static String KEY_CONNECT_HEADSET = "connectHeadset";
    private final static String KEY_CONNECT_A2DP = "connectA2dp";

    private final static int DISCONNECT_TYPE_NONE = 0;
    private final static int DISCONNECT_TYPE_MANUAL = 1;
    private final static int DISCONNECT_TYPE_ID_CHANGE = 2;
    private final static int DISCONNECT_TYPE_RESET = 3;
    private final static int DISCONNECT_TYPE_BOND = 4;
    private final static int DISCONNECT_TYPE_UPGRADING = 5;

    private boolean isScanning = false;

    private boolean getDeviceList = false;
    private String address = "";
    private boolean bindBT3 = false;
    private boolean fromQrcode = false;
    private boolean connectHeadset = false;
    private boolean connectA2dp = false;

    private int disconnectType = DISCONNECT_TYPE_NONE;

    /**
     * BleServiceCallback
     *
     * @param methodChannel methodChannel
     */
    public DeviceMessage(MethodChannel methodChannel, UTEPedEventHandler eventHandler) {
        super(methodChannel, eventHandler);
        BluetoothHelper.getInstance().setBluetoothOptionListener(this);
    }

    @Override
    public boolean methodCallConsumed(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
        if (!call.hasArgument(Const.KEY_METHOD))
            return false;

        if (super.methodCallConsumed(call, result))
            return true;

        UteBleConnection uteBleConnection = uteBleClient.getUteBleConnection();
        boolean consumed = true;
        String method = call.argument(Const.KEY_METHOD);
        assert method != null;
        Response<?> rsp;
        switch (method) {
            case M_scanDevice:
                this.getDeviceList = false;
                if (call.hasArgument(Const.KEY_TYPE)) {
                    this.getDeviceList = Const.TYPE_LIST.equals(call.argument(Const.KEY_TYPE));
                }
                if (PermissionHelper.hasBluetoothPermissions()) {
                    uteBleClient.scanDevice(this, Const.SCAN_TIME);
                    this.isScanning = true;
                }
                else {
                    PermissionHelper.getInstance().checkBluetoothPermissions(Tools.getActivity(), success -> {
                        if (success) {
                            uteBleClient.scanDevice(this, Const.SCAN_TIME);
                        }
                    });
                }

                result.success(true);
                break;
            case M_cancelScan:
                cancelScan();
                result.success(true);
                break;
            case M_connect:

                String userId = SPHelper.shareInstance().getString(Const.KEY_USERID);
                if (userId == null || userId.isEmpty()) {
                    userId = call.argument(Const.KEY_USERID);
                    SPHelper.shareInstance().putAndCommit(Const.KEY_USERID, userId);
                }

                bindBT3 = Boolean.TRUE.equals(call.argument(KEY_BIND_BT3));
                connectHeadset = Boolean.TRUE.equals(call.argument(KEY_CONNECT_HEADSET));
                connectA2dp = Boolean.TRUE.equals(call.argument(KEY_CONNECT_A2DP));
                address = call.argument(Const.KEY_ADDRESS);
                fromQrcode = Boolean.TRUE.equals(call.argument(Const.KEY_fromQrcode));

                DeviceHelper.getInstance().connect(address);
                result.success(true);
                break;
            case M_disconnect:
                boolean reset = Boolean.TRUE.equals(call.argument(Const.KEY_RESET));
                if (reset && uteBleClient.isConnectedGatt()) {
                    uteBleConnection.resetFactory(0);
                }
                disconnectDevice(DISCONNECT_TYPE_MANUAL);
                result.success(true);
                break;
            case M_getDeviceInfo:
                Map<String, Object> deviceInfoMap = new HashMap<>();
                if (uteBleClient.isConnectedGatt()) {
                    DeviceInfoRequest req = new DeviceInfoRequest();
                    req.setAddress(true);
                    req.setModel(true);
                    req.setDeviceUdId(true);
                    req.setDeviceVersion(true);
                    req.setDeviceUdId(true);
                    req.setDeviceType(true);
                    req.setPowerSaveModel(true);
                    req.setSystemVersion(true);
                    req.setLinkId(true);
                    req.setSerialNumber(true);

                    Response<DeviceInfo> deviceInfo = uteBleConnection.getDeviceInfo(req);
                    deviceInfoMap = objectToMap(deviceInfo.getData(), true, false);
                    Response<BatteryInfo> bi = uteBleClient.getUteBleConnection().getBatteryInfo();
                    deviceInfoMap.put(Const.KEY_NAME, uteBleClient.getDeviceName());
                    deviceInfoMap.put(Const.KEY_BATTERY, String.valueOf(bi.getData().getPercents()));
                    deviceInfoMap.put(Const.KEY_FIRMWARE, SPUtil.getInstance().getDeviceFirmware());
                    deviceInfoMap.put(Const.KEY_FIRMWARE_VERSION, SPUtil.getInstance().getDeviceFirmwareVersion());
                }

                d("get device info:" + deviceInfoMap);
                result.success(deviceInfoMap);
                break;

            case M_getBatteryInfo:
                Map<String, Object> batteryInfoMap = new HashMap<>();
                if (uteBleClient.isConnectedGatt()) {
                    Response<BatteryInfo> batteryInfo = uteBleConnection.getBatteryInfo();
                    batteryInfoMap = objectToMap(batteryInfo.getData(), true, false);
                }

                d("get battery info:" + batteryInfoMap);
                result.success(batteryInfoMap);
                break;

            case M_getBondedDevices:
                Set<BluetoothDevice> bondedDevices = uteBleClient.getBondedDevices();
                List<Map<String, Object>> deviceList = new ArrayList<>();
                for (BluetoothDevice device: bondedDevices) {
                    deviceList.add(deviceToMap(device));
                }
                result.success(deviceList);
                break;

            case M_getDefaultConfiguration:
                Map<String, Object> configurationMap = new HashMap<>();
                if (uteBleClient.isConnectedGatt()) {
                    Response<DefaultConfiguration> configuration = uteBleConnection.getDefaultConfiguration();
                    configurationMap = objectToMap(configuration.getData(), true, false);
                    d("get default configuration:" + configuration);
                }
                result.success(configurationMap);
                break;

            case Const.M_setCameraStatus:
                int status = call.argument(Const.KEY_STATUS);
                boolean success = false;
                if (uteBleClient.isConnectedGatt()) {
                    rsp = uteBleConnection.setCameraStatus(status);
                    success = (rsp.getErrorCode() == Const.CODE_SUCCESS);
                    if (success && status == CameraStatusConfig.STATUS_CLOSE) {
                        CameraRemind.shareInstance().resetCameraStatus();
                    }
                }
                result.success(success);
                break;

            case M_turnBluetooth:
                boolean enable = Boolean.TRUE.equals(call.argument(Const.KEY_ENABLE));
                if (enable) {
                    BluetoothHelper.getInstance().turnOn();
                }
                else {
                    BluetoothHelper.getInstance().turnOff();
                }
                break;

            default:
                consumed = false;
        }

        return consumed;
    }

    @Override
    public String getPath() {
        return PATH;
    }

    /**
     *
     * @param status
     *     int STATE_DISCONNECTED = 0;
     *     int STATE_CONNECTING = 1;
     *     int STATE_CONNECTED = 2;
     */
    @Override
    public void onConnecteStateChange(int status) {
        d("onConnecteStateChange:" + status);
        Map<String, Object> map = new HashMap<>();
        map.put(Const.KEY_PATH, getPath());
        if (status == STATE_DISCONNECTED) {
            if (DeviceHelper.getInstance().isUpgradingCompleteSoon()) {
                this.disconnectType = DISCONNECT_TYPE_UPGRADING;
            }

            map.put(Const.KEY_METHOD, M_disconnect);
            map.put(Const.KEY_TYPE, this.disconnectType);
            switch (disconnectType) {
                case DISCONNECT_TYPE_MANUAL:
                    DeviceHelper.getInstance().onDeviceDisconnect(address);
                    address = "";
                    break;

                case DISCONNECT_TYPE_ID_CHANGE:
                case DISCONNECT_TYPE_RESET:
                case DISCONNECT_TYPE_BOND:
                    DeviceHelper.getInstance().onDeviceDisconnect(address);
                    address = "";
                    disconnectDevice(disconnectType);

                    break;
            }
            this.disconnectType = DISCONNECT_TYPE_NONE;
        }
        else {
            map.put(Const.KEY_METHOD, M_connect);
        }
        map.put(Const.KEY_STATUS, status);
        map.put(Const.KEY_ADDRESS, this.uteBleClient.getDeviceAddress());
        map.put(Const.KEY_NAME, this.uteBleClient.getDeviceName());
        map.put(Const.JL_OTA_STAGE, SPHelper.shareInstance().getBoolean(Const.JL_OTA_STAGE, false));
        eventHandler.publishEvent(map);

        String address = uteBleClient.getDeviceAddress();

        if (status == STATE_CONNECTED) {
            this.disconnectType = DISCONNECT_TYPE_NONE;
            DeviceHelper.getInstance().initPref(address);
            uteBleClient.getUteBleConnection().setTimeClock(getTimeClock());
            uteBleClient.getUteBleConnection().setTimeDisplay(getTimeDisplay());
            Response<?> rsp = uteBleClient.getUteBleConnection().openDeviceBt3(true);
            d("openDeviceBt3 failed: " + rsp.getErrorCode());
            rsp = uteBleClient.getUteBleConnection().requestDevicePairBt3();
            d("requestDevicePairBt3 failed: " + rsp.getErrorCode());

            if (bindBT3) {
                BluetoothHelper.getInstance().bindBT3(uteBleClient.getBluetoothDevice(), connectHeadset, connectA2dp);
            }
        }
    }

    @Override
    public void onScanning(UteScanDevice uteScanDevice) {
        d("onScanning");
        if (this.getDeviceList || !isValidDevice(uteScanDevice.getDevice()))
            return;

        Map<String, Object> map = deviceToMap(uteScanDevice.getDevice());
        map.put(Const.KEY_PATH, getPath());
        map.put(Const.KEY_METHOD, M_scanDevice);
        map.put(Const.KEY_TYPE, Const.TYPE_MAP);
        eventHandler.publishEvent(map);
    }

    @Override
    public void onScanComplete(List<UteScanDevice> list) {
        d("onScanComplete...get device list:" + getDeviceList);
        d(list);
        this.isScanning = false;
        if (!this.getDeviceList)
            return;

        DeviceHelper.getInstance().setScanDevices(list);

        List<Map<String, Object>> deviceList = new ArrayList<>();
        for (UteScanDevice uteScanDevice : list) {
            if (isValidDevice(uteScanDevice.getDevice())) {
                Map<String, Object> deviceMap = deviceToMap(uteScanDevice.getDevice());
                deviceList.add(deviceMap);
            }
        }
        Map<String, Object> map = new HashMap<>();
        map.put(Const.KEY_PATH, getPath());
        map.put(Const.KEY_METHOD, M_scanDevice);
        map.put(Const.KEY_TYPE, Const.TYPE_LIST);
        map.put(Const.KEY_LIST, deviceList);
        eventHandler.publishEvent(map);
    }

    @Override
    public void onScanFailed(int errorCode) {
        d("onScanFailed");
        eventHandler.publishFailed(String.valueOf(errorCode), "onScanFailed", getPath());
    }

    private boolean isValidDevice(BluetoothDevice device) {
        String deviceName = device.getName();
        boolean enableFilter = SPHelper.shareInstance().getBoolean(Const.M_setDeviceFilter, true);
        if (!enableFilter) {
            return !TextUtils.isEmpty(deviceName);
        }

        if (!TextUtils.isEmpty(deviceName)
                && (deviceName.startsWith("Callie")
                || deviceName.startsWith("Helio")
                || deviceName.startsWith("MEGA")
                || deviceName.startsWith("Vibe Fit"))) {
            return true;
        }

        return false;
    }

    private Map<String, Object> deviceToMap(BluetoothDevice device) {
        Map<String, Object> map = new HashMap<>();
        map.put(Const.KEY_NAME, device.getName());
        map.put(Const.KEY_ADDRESS, device.getAddress());

        return map;
    }

    @Override
    public void onNotify(@NonNull UteBleDevice uteBleDevice, @NonNull Notify notify) {
        d("onNotify: " + new Gson().toJson(notify));
        int eventType = notify.getType();
        Map<String, Object> map;
        switch (eventType) {
            case NotifyType.DEVICE_PAIRED_STATE_NOTIFY:
                DevicePairedState devicePairedState = (DevicePairedState) notify.getData();
                if (devicePairedState.getPairedState() == 1) {
                    HonorAccountConfig accountConfig = new HonorAccountConfig();
                    String userId = SPHelper.shareInstance().getString(Const.KEY_USERID);
                    d("onNotify userId:" + userId);
                    //用户唯一ID
                    accountConfig.setCurrentHuid(userId);
//                    this.disconnectType = DISCONNECT_TYPE_ID_CHANGE;
                    this.uteBleClient.getUteBleConnection().setHonorAccount(accountConfig);
                } else {
//                    if (fromQrcode) {
//                        handler.postDelayed(() -> DeviceHelper.getInstance().connect(address), 7 * 1000L);
//                    }
//                    else {
//                        disconnectDevice(DISCONNECT_TYPE_ID_CHANGE);
//                    }
                    disconnectDevice(DISCONNECT_TYPE_ID_CHANGE);
                    notifyDisconnectEvent(DISCONNECT_TYPE_ID_CHANGE);
                }
                break;
            case NotifyType.DEVICE_RESET_NOTIFY:
                DeviceResetNotify deviceResetNotify =  (DeviceResetNotify) notify.getData();
                LogUtils.i("deviceResetNotify, getResetResult = " + deviceResetNotify.getResetResult());
                if (deviceResetNotify.getResetResult()) {
//                    handler.postDelayed(this::reconnect, 8 * 1000L);
                } else {
                    disconnectDevice(DISCONNECT_TYPE_RESET);
                    notifyDisconnectEvent(DISCONNECT_TYPE_RESET);
                }

                break;

            case NotifyType.DEVICE_BATTERY_REPORT:
                BatteryInfo batteryInfo = (BatteryInfo) notify.getData();
                map = objectToMap(batteryInfo, true, false);
                map.put(Const.KEY_PATH, getPath());
                map.put(Const.KEY_METHOD, M_getBatteryInfo);
                map.put(Const.KEY_ADDRESS, uteBleDevice.getDeviceAddress());
                map.put(Const.KEY_BATTERY, String.valueOf(batteryInfo.getPercents()));
                eventHandler.publishEvent(map);
                break;

            case NotifyType.SCREEN_AUTO_LIGHT:
                ScreenAutoLightSwitch screenAutoLightSwitch = (ScreenAutoLightSwitch) notify.getData();
                map = objectToMap(screenAutoLightSwitch, true, false);
                map.put(Const.KEY_PATH, getPath());
                map.put(Const.KEY_METHOD, Const.M_setScreenAutoLight);
                map.put(Const.KEY_ENABLE, screenAutoLightSwitch.switchState == 1);
                eventHandler.publishEvent(map);
                break;

            case NotifyType.FIND_WEAR_STATE:
                FindWearState findWearState = (FindWearState) notify.getData();
                map = new HashMap<>();
                map.put(Const.KEY_PATH, getPath());
                map.put(Const.KEY_METHOD, Const.M_getFindWearState);
                map.put(Const.KEY_VALUE, findWearState.getFindWearState());
                eventHandler.publishEvent(map);
                break;

            case NotifyType.CAMERA_CONTROL:
                CameraControl cameraControl = (CameraControl) notify.getData();
                map = new HashMap<>();
                map.put(Const.KEY_PATH, getPath());
                map.put(Const.KEY_METHOD, Const.M_cameraControl);
                map.put(Const.KEY_VALUE, cameraControl.getInstruction());
                eventHandler.publishEvent(map);
                break;
                
            case NotifyType.WEATHER_DATA_REQUIRED:
                map = new HashMap<>();
                map.put(Const.KEY_PATH, getPath());
                map.put(Const.KEY_METHOD, Const.M_setFutureWeather);
                map.put(Const.KEY_VALUE, notify.getType());
                eventHandler.publishEvent(map);
                break;

            case NotifyType.JL_OTA_SECOND_MODE:
                JLOtaSecondModeNotify jlOtaSecondModeNotify = (JLOtaSecondModeNotify) notify.getData();
                SPHelper.shareInstance().putAndCommit(Const.JL_OTA_STAGE, jlOtaSecondModeNotify.getSuccess());
                break;
        }
    }

    private void cancelScan() {
        if (!isScanning) {
            d("not on scanning");
            return;
        }
        if (uteBleClient != null) {
            uteBleClient.cancelScan();
        }
    }

    private void disconnectDevice(int type) {
        d("disconnect type " +  type);
        this.disconnectType = type;
        if (this.disconnectType != DISCONNECT_TYPE_NONE) {
            DeviceHelper.getInstance().cancelCheckTimer();
        }
        if (disconnectType == DISCONNECT_TYPE_MANUAL) {//need BLUETOOTH_CONNECT permission
            try {
                BluetoothHelper.getInstance().unbindBT3();
                if (this.uteBleClient.isConnected()) {
                    this.uteBleClient.disconnect();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            // show toast
            d("no device connected");
        }
    }

    private void notifyDisconnectEvent(int disconnectType) {
        Map<String, Object> map = new HashMap<>();
        map.put(Const.KEY_PATH, getPath());
        map.put(Const.KEY_METHOD, M_disconnect);
        map.put(Const.KEY_TYPE, disconnectType);
        map.put(Const.KEY_STATUS, STATE_DISCONNECTED);
        map.put(Const.KEY_ADDRESS, this.uteBleClient.getDeviceAddress());
        map.put(Const.KEY_NAME, this.uteBleClient.getDeviceName());

        eventHandler.publishEvent(map);
    }

    @Override
    public void onUnbind(boolean success) {
        this.uteBleClient.disconnect();
    }

    @Override
    public void onBondState(int state) {
        if (state == BluetoothDevice.BOND_NONE && DeviceHelper.getInstance().read(KEY_BIND_BT3, false)) {
//            this.disconnectType = DISCONNECT_TYPE_BOND;
        }
    }
}
